/*******************************************************************
 *
 * Copyright (C), 2021-2022, LS, All rights reserved.
 *
 * Author      : 
 * Create Time : 2021-09-17
 * Version     : v0.1
 * Description : 
 *******************************************************************/

/*
 * INCLUDE FILES
 ****************************************************************************************
 */
#include "string.h"
#include "bcomdef.h"
#include "OSAL.h"
#include "linkdb.h"
#include "ll_def.h"
#include "att.h"
#include "gatt.h"
#include "gatt_uuid.h"
#include "peripheral.h"
#include "gattservapp.h"

#include "ls_ble_type.h"
#include "ls_ble_ota.h"
#include "log.h"


/*********************************************************************
 * MACROS
 */
#define OTA_SERVICE_UUID         0x12, 0xD5, 0xBC, 0xEA, 0x5F, 0x98, 0x23, 0x23, 0xDE, 0xEF, 0xAA, 0x55, 0x30, 0x56, 0x00, 0x00
#define OTA_SERVICE_CP_UUID      0x12, 0xD5, 0xBC, 0xEA, 0x5F, 0x98, 0x23, 0x23, 0xDE, 0xEF, 0xAA, 0x55, 0x31, 0x56, 0x00, 0x00
#define OTA_SERVICE_PKT_UUID     0x12, 0xD5, 0xBC, 0xEA, 0x5F, 0x98, 0x23, 0x23, 0xDE, 0xEF, 0xAA, 0x55, 0x32, 0x56, 0x00, 0x00
#define OTA_SERVICE_VER_UUID     0x12, 0xD5, 0xBC, 0xEA, 0x5F, 0x98, 0x23, 0x23, 0xDE, 0xEF, 0xAA, 0x55, 0x33, 0x56, 0x00, 0x00

#define OTA_PROFILE_CTRL_POINT_HANDLE           2
#define OTA_PROFILE_CTRL_POINT_CFG_HANDLE       3

/*********************************************************************
 * CONSTANTS
 */


/*********************************************************************
 * TYPEDEFS
 */

/*********************************************************************
 * GLOBAL VARIABLES
 */
// OTA GATT Profile Service UUID
CONST uint8_t otaProfileServUUID[ATT_UUID_SIZE] =
{ 
	OTA_SERVICE_UUID
};

// OTA Control Point
CONST uint8_t otaProfileCtrlPointUUID[ATT_UUID_SIZE] =
{ 
	OTA_SERVICE_CP_UUID
};

// OTA Packet
CONST uint8_t otaProfilePacketUUID[ATT_UUID_SIZE] =
{ 
	OTA_SERVICE_PKT_UUID
};

// OTA Revision
CONST uint8_t otaProfileRevisionUUID[ATT_UUID_SIZE] =
{ 
	OTA_SERVICE_VER_UUID
};

/*********************************************************************
 * EXTERNAL VARIABLES
 */

/*********************************************************************
 * EXTERNAL FUNCTIONS
 */

/*********************************************************************
 * LOCAL VARIABLES
 */

static ota_profile_cb_t *ota_profile_cb = NULL;

/*********************************************************************
 * Profile Attributes - variables
 */

// OTA Profile Service attribute
static CONST gattAttrType_t otaProfileService = { ATT_UUID_SIZE, otaProfileServUUID };

// OTA Profile OTA Control Point Properties
static uint8_t otaProfileCtlPointProps = GATT_PROP_WRITE | GATT_PROP_NOTIFY;

// OTA Profile OTA Control Point Value
static uint8_t otaProfileCtlPointValue[OTA_MAX_DATA_LEN];

// OTA Profile OTA Control Point client configuration
static gattCharCfg_t otaProfileCtlPointConfig[GATT_MAX_NUM_CONN];

// OTA Profile OTA Packet Properties
static uint8_t otaProfilePacketProps = GATT_PROP_WRITE_NO_RSP;

// OTA Profile OTA Packet Value
static uint8_t otaProfilePacketValue[OTA_MAX_DATA_LEN];

// OTA Profile OTA Revision Properties
static uint8_t otaProfileRevisionProps = GATT_PROP_READ;

// OTA Profile OTA Revision Value
static uint8_t otaProfileRevisionValue[2] = {0x00, 0x01};

/*********************************************************************
 * Profile Attributes - Table
 */

static gattAttribute_t otaProfileAttrTbl[] = 
{
  // OTA Profile Service
  { 
    { ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
    GATT_PERMIT_READ,                         /* permissions */
    0,                                        /* handle */
    (uint8_t *)&otaProfileService            /* pValue */
  },

    // OTA Control Point Declaration
    { 
      { ATT_BT_UUID_SIZE, characterUUID },
      GATT_PERMIT_READ, 
      0,
      &otaProfileCtlPointProps 
    },

      // OTA Control Point Value
      { 
        { ATT_UUID_SIZE, otaProfileCtrlPointUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE, 
        0, 
        otaProfileCtlPointValue 
      },
	  
	  // OTA Control Point configuration
      { 
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE, 
        0, 
        (uint8_t *)&otaProfileCtlPointConfig 
      },  

    // OTA Packet Declaration
    { 
      { ATT_BT_UUID_SIZE, characterUUID },
      GATT_PERMIT_READ, 
      0,
      &otaProfilePacketProps 
    },

      // OTA Packet Value
      { 
        { ATT_UUID_SIZE, otaProfilePacketUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE, 
        0, 
        otaProfilePacketValue 
      },     
	  
	 // OTA Revision Declaration
    { 
      { ATT_BT_UUID_SIZE, characterUUID },
      GATT_PERMIT_READ, 
      0,
      &otaProfileRevisionProps 
    },

      // OTA Revision Value
      { 
        { ATT_UUID_SIZE, otaProfileRevisionUUID },
        GATT_PERMIT_READ, 
        0, 
        otaProfileRevisionValue 
      },     
};

/*********************************************************************
 * LOCAL FUNCTIONS
 */
static bStatus_t otaProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t* pAttr, uint8* pValue, uint16 len, uint16 offset );
static bStatus_t otaProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t* pAttr, uint8* pValue, uint16* pLen, uint16 offset, uint8 maxLen );
static void otaProfile_ConnStatusCB( uint16_t connHandle, uint8_t changeType );

/*********************************************************************
 * PROFILE CALLBACKS
 */
// OTA Profile Service Callbacks
CONST gattServiceCBs_t otaProfileCBs =
{
    otaProfile_ReadAttrCB,  // Read callback function pointer
    otaProfile_WriteAttrCB, // Write callback function pointer
    NULL                    // Authorization callback function pointer
};

/*********************************************************************
 * PUBLIC FUNCTIONS
 */


int ota_service_init(ota_profile_cb_t *cb)
{
    // Register with Link DB to receive link status change callback
    VOID linkDB_Register( otaProfile_ConnStatusCB  );  

    // Initialize Client Characteristic Configuration attributes
    GATTServApp_InitCharCfg( INVALID_CONNHANDLE, otaProfileCtlPointConfig );

    // Register GATT attribute list and CBs with GATT Server App
    GATTServApp_RegisterService( otaProfileAttrTbl, 
                                      GATT_NUM_ATTRS( otaProfileAttrTbl ),
                                      &otaProfileCBs );
    if(cb)
    {
        ota_profile_cb = cb;
    }
    return LS_BLE_SUCCESS;
}


int ota_notify_tx_data(uint8_t *p_data, uint16_t length)
{
    uint16_t connHandle;
    uint16_t value;
    attHandleValueNoti_t ntf;

    if(length > ATT_MTU_SIZE) {
        return LS_BLE_ERR_INVALID_LENGTH;
    }
    
    GAPRole_GetParameter(GAPROLE_CONNHANDLE, &connHandle);
    if(connHandle == INVALID_CONNHANDLE)
        return LS_BLE_ERR_CONN_INVALID;

    value = GATTServApp_ReadCharCfg( connHandle, otaProfileCtlPointConfig);
    // If notifications enabled
    if ( value & GATT_CLIENT_CFG_NOTIFY )
    {
        // Set the handle
        ntf.handle = otaProfileAttrTbl[OTA_PROFILE_CTRL_POINT_HANDLE].handle;
        memcpy(ntf.value, p_data, length);
        ntf.len = length;
        // Send the Notify
        if(SUCCESS == GATT_Notification( connHandle, &ntf, FALSE)) {
            return LS_BLE_SUCCESS;
        }
    }

    LOG("ota_notify_tx_data fail.\n");
    return LS_BLE_ERR_NTF_FAIL;
}


static bStatus_t otaProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t* pAttr, uint8* pValue, uint16* pLen, uint16 offset, uint8 maxLen )
{
    bStatus_t status = SUCCESS;

    // If attribute permissions require authorization to read, return error
    if ( gattPermitAuthorRead( pAttr->permissions ) )
    {
        // Insufficient authorization
        return ( ATT_ERR_INSUFFICIENT_AUTHOR );
    }

    // Make sure it's not a blob operation (no attributes in the profile are long)
    if ( offset > 0 )
    {
        return ( ATT_ERR_ATTR_NOT_LONG );
    }

    if ( pAttr->type.len == ATT_BT_UUID_SIZE )
    {
        // 16-bit UUID
        uint16_t uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
        switch ( uuid )
        {
            default:
                // Should never get here! (characteristics 3 and 4 do not have read permissions)
                *pLen = 0;
                status = ATT_ERR_ATTR_NOT_FOUND;
            break;
        }
    }
    else
    {
        // 128-bit UUID
        if(pAttr->type.uuid == otaProfileRevisionUUID) {
            *pLen = 2;
            VOID osal_memcpy( pValue, pAttr->pValue, 2 );
        }
        else {
            *pLen = 0;
            status = ATT_ERR_ATTR_NOT_FOUND;
        }
    }

    return ( status );
}

/*********************************************************************
 * @fn      otaProfile_WriteAttrCB
 *
 * @brief   Validate attribute data prior to a write operation
 *
 * @param   connHandle - connection message was received on
 * @param   pAttr - pointer to attribute
 * @param   pValue - pointer to data to be written
 * @param   len - length of data
 * @param   offset - offset of the first octet to be written
 *
 * @return  Success or Failure
 */
//static bStatus_t wristProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t* pAttr, uint8* pValue, uint16 len, uint16 offset )
static bStatus_t otaProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t* pAttr, uint8* pValue, uint16 len, uint16 offset )
{
    bStatus_t status = SUCCESS;
    ota_evt_t evt = {
        .evt_type = OTA_EVT_INVALID,
        .length = 0
    };

    // If attribute permissions require authorization to write, return error
    if ( gattPermitAuthorWrite( pAttr->permissions ) )
    {
        // Insufficient authorization
        return ( ATT_ERR_INSUFFICIENT_AUTHOR );
    }
    if(pAttr->type.len == ATT_BT_UUID_SIZE) 
    {
        uint16_t uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
        switch ( uuid )
        {
            case GATT_CLIENT_CHAR_CFG_UUID:
                status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,
                                                 offset, GATT_CLIENT_CFG_NOTIFY );
                if(status == SUCCESS && pAttr->handle == otaProfileAttrTbl[OTA_PROFILE_CTRL_POINT_CFG_HANDLE].handle)
                {
                    uint16_t charCfg = BUILD_UINT16( pValue[0], pValue[1] );
                    evt.evt_type = (charCfg == 0) ? OTA_EVT_CP_NOTIFICATION_DISABLED : OTA_EVT_CP_NOTIFICATION_ENABLED;
                }
            break;
        
            default:
                // Should never get here! (characteristics 2 and 4 do not have write permissions)
                status = ATT_ERR_ATTR_NOT_FOUND;
            break;
        }
    }
    else
    {
        // 128-bit UUID
        if(pAttr->type.uuid == otaProfileCtrlPointUUID || pAttr->type.uuid == otaProfilePacketUUID) {
            //Write the value
            if ( status == SUCCESS )
            {
                if( pAttr->pValue == otaProfileCtlPointValue )
                {
                    evt.evt_type = OTA_EVT_CP_RECEIVE_DATA;   
                    evt.length = len;
                    evt.p_data = pValue;
                }
                else if( pAttr->pValue == otaProfilePacketValue)
                {
                    evt.evt_type = OTA_EVT_PKT_RECEIVE_DATA; 
                    evt.length = len;
                    evt.p_data = pValue;
                }
            }
        }
    }

    if(ota_profile_cb && OTA_EVT_INVALID != evt.evt_type) {
        ota_profile_cb->evt_handler(&evt);
    }
    
    return status;
}


static void otaProfile_ConnStatusCB( uint16_t connHandle, uint8_t changeType )
{ 
    ota_evt_t evt = {
        .evt_type = OTA_EVT_INVALID,
        .length = 0
    };

    // Make sure this is not loopback connection
    if ( connHandle != LOOPBACK_CONNHANDLE )
    {
        // Reset Client Char Config if connection has dropped
        if ( ( changeType == LINKDB_STATUS_UPDATE_REMOVED )      ||
         ( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) && 
           ( !linkDB_Up( connHandle ) ) ) )
        {
            GATTServApp_InitCharCfg( connHandle, otaProfileCtlPointConfig);
            //disconnect
            evt.evt_type = OTA_EVT_DISCONNECT;
        }
        else if(( changeType == LINKDB_STATUS_UPDATE_NEW )      ||
         ( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) && 
           ( linkDB_Up( connHandle ) ) ) ) 
        {
            //connect
            evt.evt_type = OTA_EVT_CONNECTED;
        }
    }
    
    if(ota_profile_cb && OTA_EVT_INVALID != evt.evt_type) {
        ota_profile_cb->evt_handler(&evt);
    }
}

